/***************************************************************************
 *
 *   copyright            : (C) 2005 Winds of Storm
 *
 *   $Id: main.c,v 1.18 2007/02/10 12:40:34 nathan Exp $
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

#include "sb.h"

#ifndef lint
static char id[] = "$Id: main.c,v 1.18 2007/02/10 12:40:34 nathan Exp $";
#endif

/*
 * Global variables
 */
DB_CONN		*dbconn;
FILE		*logfile;
pthread_mutex_t	db_mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t	desc_mutex=PTHREAD_MUTEX_INITIALIZER;
char		*db_user = NULL;
char		*db_pass = NULL;
char		*db_name = NULL;
char		*db_host = NULL;
char		*db_pfx = NULL;
char		*site_url = NULL;
char		*site_domain = NULL;
int		port = DEFAULT_PORT;
int		last_event = 0;
int		listener;
int		time_idle = 0, time_limbo = 0, time_warn = 0, time_timeout = 0;

int main (int argc, char **argv) {
	char	*log_filename = "-";
	char	*config_filename = NULL;
	char	*arglist = "f:p:l:vcV";
	char	buffer[MAX_STRING_LENGTH];
	int	c;
	int	check_only = 0;
	FILE	*pfile;

	// Turn on memory tracing
	//mtrace();

	mud_verbosity	= 0;
	/* Process command line options */
	while ((c = getopt(argc, argv, arglist)) != -1) {
		switch (c) {
			case 'p':
				if (optarg == NULL || *optarg == '\0') {
					fprintf(stderr, "%s: Illegal option after port reference.\n", PROGNAME);
					exit(1);
				}
				port	= atoi(optarg);
				break;
			case 'v':
				mud_verbosity++;
				break;
			case 'l':
				if (optarg == NULL || *optarg == '\0') {
					fprintf(stderr, "%s: Illegal option after logfile reference.\n", PROGNAME);
					exit(1);
				}
				log_filename	= optarg;
				break;
			case 'V':
				fprintf(stderr, "%s: Version %s\n", PROGNAME, VERSION);
				exit(1);
				break;
			case 'f':
				if (optarg == NULL || *optarg == '\0') {
					fprintf(stderr, "%s: Illegal option after config file reference.\n", PROGNAME);
					exit(1);
				}
				config_filename	= optarg;
				break;
			case 'c':
				check_only = 1;
				break;
		}
	}

	if ((pfile = fopen("sbchat.pid", "w")) == NULL) {
		fprintf(stderr, "Couldn't open sbchat.pid: %m!\n");
		exit(1);
	}
	fprintf(pfile, "%d\n", (int)getpid());
	fclose(pfile);

	if (config_filename == NULL)
		config_filename	= strdup("./sbchat.cnf");
	bootstrap_logging(log_filename);
	bootstrap_config(config_filename);
	bootstrap_corelimit();
	bootstrap_signals();
	if (!bootstrap_db()) {
		sb_log(LOG_ERR, "Can't connect to database, shutting down");
		exit(-1);
	}
	bootstrap_db_config();
	bootstrap_db_lastevent();

	// clean old sessions from before startup...
	sprintf(buffer, "DELETE FROM %ssessions WHERE server_push IN (%d, %d) OR room_id<0", db_pfx, MODE_SERVER_PUSH, MODE_SERVER_PUSH_PROXY);
	db_query(dbconn, buffer);

	// note that chat is starting, for people that use client-pull
	sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, user_id_to, event_text, event_date, event_chat_flags) VALUES (0, -1, 0, '<b>*** Chat server starting...</b>', NOW(), 2)", db_pfx);
	db_query(dbconn, buffer);

	bootstrap_chat(port);
	shutdown_chat();

	exit(1);
}

void bootstrap_db_config(void) {
	char	buffer[MAX_STRING_LENGTH];
	DB_RES  *results = NULL;
	DB_ROW  row = NULL;
	int	num_rows = 0;
	int	i, errors = 0;

	sprintf(buffer, "SELECT config_name,config_value FROM %sconfig", db_pfx); // WHERE config_name LIKE 'TIME_%%'");
	results	= db_query(dbconn, buffer);
	if (results != NULL) {
		num_rows	= db_num_rows(results);
		for (i = 0; i < num_rows; i++) {
			int	field_known = 0;
			row	= db_fetch_row(results, i);
			if (!strcasecmp("TIME_IDLE", row[0])) {
				time_idle	= atoi(row[1]);
				field_known++;
			} else if (!strcasecmp("TIME_LIMBO", row[0])) {
				time_limbo	= atoi(row[1]);
				field_known++;
			} else if (!strcasecmp("TIME_WARN", row[0])) {
				time_warn	= atoi(row[1]);
				field_known++;
			} else if (!strcasecmp("TIME_TIMEOUT", row[0])) {
				time_timeout	= atoi(row[1]);
				field_known++;
			} else if (!strcasecmp("CHAT_PORT", row[0])) {
				port		= atoi(row[1]);
				field_known++;
			} else if (!strcasecmp("CHAT_URL", row[0])) {
				site_url	= strdup(row[1]);
				field_known++;
			} else if (!strcasecmp("DOMAIN", row[0])) {
				site_domain	= strdup(row[1]);
				field_known++;
			}
			if (mud_verbosity && field_known) {
				sb_log(LOG_DEBUG, "Set %s to %s\n", row[0], row[1]);
			}
		}
		if (!port)	{ errors++;	sb_log(LOG_CRIT, "Chat Port must be set in the database!\n");	}
		if (!site_url)	{ errors++;	sb_log(LOG_CRIT, "Chat URL must be set in the database!\n");	}
	} else {
		errors++;
		sb_log(LOG_CRIT, "Unable to pull config values from database, aborting.");
	}
	if (errors)
		exit(1);
}

void bootstrap_config(char *filename) {
	char	linebuffer[MAX_STRING_LENGTH];
	char	buffer[MAX_STRING_LENGTH];
	char	buffer2[MAX_STRING_LENGTH];
	FILE	*cfile;
	int	line = 0;
	int	errors = 0;

	if (filename == NULL) {
		fprintf(stderr, "%s: Specify -f <filename>\n", PROGNAME);
		exit(1);
	}

	if ((cfile = fopen(filename, "r")) == NULL) {
		fprintf(stderr, "Couldn't open %s: %m!\n", filename);
		exit(1);
	}

	while (!feof(cfile)) {
		line++;
		//fprintf(stderr, "Reading line %d of %s\n", line, filename);
		fgets(linebuffer, MAX_STRING_LENGTH, cfile);
		if (mud_verbosity)
			fprintf(stderr, "Line = %s", linebuffer);
		sscanf(linebuffer, "%s = %s\n", buffer, buffer2);
		//fprintf(stderr, "buffer 1 = %s\n", buffer);
		//fprintf(stderr, "buffer 2 = %s\n", buffer2);

		switch(buffer[0]) {
			case '#':
			case ';':
			case '/':
				// ignore comments
				break;
			case 'u':
			case 'U':
				if (!strcasecmp(buffer, "username")) {
					db_user	= strdup(buffer2);
				} else {
					fprintf(stderr, "%s: Unknown config parameter '%s'\n", PROGNAME, buffer);
				}
				break;
			case 'd':
			case 'D':
				if (!strcasecmp(buffer, "dbname")) {
					db_name	= strdup(buffer2);
				} else if (!strcasecmp(buffer, "dbprefix")) {
					db_pfx	= strdup(buffer2);
				} else {
					fprintf(stderr, "%s: Unknown config parameter '%s'\n", PROGNAME, buffer);
				}
				break;
			case 'p':
			case 'P':
				if (!strcasecmp(buffer, "password")) {
					db_pass	= strdup(buffer2);
				} else {
					fprintf(stderr, "%s: Unknown config parameter '%s'\n", PROGNAME, buffer);
				}
				break;
			case 's':
			case 'S':
				if (!strcasecmp(buffer, "server")) {
					db_host	= strdup(buffer2);
				} else {
					fprintf(stderr, "%s: Unknown config parameter '%s'\n", PROGNAME, buffer);
				}
				break;
			default:
				fprintf(stderr, "%s: Unknown config parameter '%s'\n", PROGNAME, buffer);
				break;
		}
	}
	
	if (mud_verbosity) {
		if (db_user)	{ fprintf(stdout, "DB User	= %s\n", db_user);	}
		if (db_pass)	{ fprintf(stdout, "DB Pass	= %s\n", db_pass);	}
		if (db_name)	{ fprintf(stdout, "DB Name	= %s\n", db_name);	}
		if (db_host)	{ fprintf(stdout, "DB Host	= %s\n", db_host);	}
		if (db_pfx)	{ fprintf(stdout, "DB Prefix	= %s\n", db_pfx);	}
	}

	if (!db_user)	{ errors++;	fprintf(stdout, "DB User must be set in the config file!\n");		}
	if (!db_pass)	{ errors++;	fprintf(stdout, "DB Pass must be set in the config file!\n");		}
	if (!db_name)	{ errors++;	fprintf(stdout, "DB Name must be set in the config file!\n");		}
	if (!db_host)	{ errors++;	fprintf(stdout, "DB Host must be set in the config file!\n");		}
	if (!db_pfx)	{ errors++;	fprintf(stdout, "DB Prefix must be set in the config file!\n");		}

	fclose(cfile);

	if (errors)
		exit(1);
}

void bootstrap_corelimit(void) {
	//
	// Core files are invaluable for debugging a server, so try to set
	// limits appropriately to be able to generate them even if the
	// shell normally disables them.
	//
	struct rlimit cur_limits;

	getrlimit(RLIMIT_CORE, &cur_limits);
	sb_log(LOG_INFO, "Current core limit is: %d", cur_limits.rlim_cur);
	cur_limits.rlim_max	= RLIM_INFINITY;
	cur_limits.rlim_cur	= cur_limits.rlim_max;
	if (setrlimit(RLIMIT_CORE, &cur_limits) == -1) {
		// Didn't take
		sb_log(LOG_WARNING, "Couldn't change core limits.");
	} else {
		sb_log(LOG_INFO, "Core limits changed successfully.");
	}
}

//
// Handle exitting child processes
//
void child_handler (int sig) {
	while( wait(NULL) > 0);
}


void shutdown_handler (int sig) {
	DESCRIPTOR_DATA	*tmp_desc;

	for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc->next) {
		descriptor_write(tmp_desc, "<script>SendToChat('*** SHUTDOWN: Chat server shutdown, please click <a target=_top href=");
		descriptor_write(tmp_desc, site_url);
		descriptor_write(tmp_desc, ">here</a> to log back in.');</script>\n");
		close(tmp_desc->descriptor);
	}

	shutdown_chat();
}

void alarm_handler (int sig) {
	sb_log(LOG_EMERG, "-=- ALARM TIMEOUT, DYING WITH PREJUDICE -=-");

	exit(-1);
}

//
// Error handler
//
void error_handler (int sig) {
	pid_t		forkpid;
	DESCRIPTOR_DATA	*tmp_desc;
	struct sigaction alarm_sa;

	sb_log(LOG_CRIT, "Hit error handler...");

	//
	// Reset the signal... so if we crash during the crash handling code it doesn't get re-executed.
	//
	
	signal(SIGSEGV, SIG_DFL);
	signal(SIGALRM, SIG_DFL);
	sb_log(LOG_CRIT, "Restored default error handlers...");

	// 
	// Set a real alarm signal handler...
	//
	
	alarm_sa.sa_handler	= alarm_handler;
	sigemptyset(&alarm_sa.sa_mask);
	alarm_sa.sa_flags	= SA_RESTART;
	sigaction( SIGALRM,	&alarm_sa,	NULL );

	sb_log(LOG_CRIT, "Set alarm signal handler...");

	//
	// Set an alarm so if fork() locks up, the alarm will interrupt and chat'll restart anyways...
	//
	alarm(60);

	//
	// Fork... inside the fork we will call SIGABRT so we get a core dump that reflects where the problem occurred
	//
	
	if ((forkpid = fork()) <= 0) {
		sb_log(LOG_CRIT, "Forked process %d to raise SIGSEGV...", getpid());
		raise(SIGSEGV);
		return;
	}

	sb_log(LOG_CRIT, "After fork of aborting process...");

	// 
	// Kill scheduled alarm
	//
	alarm(0);

	signal(SIGALRM,	SIG_IGN);

	sb_log(LOG_CRIT, "Reset alarm signal handler...");

	//
	// Wait for the fork'd process to exit
	//
	
	wait(NULL);
	sb_log(LOG_CRIT, "Core dumping process finished...");

	//
	// Let people know to log back in.
	//
	//
	
	for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc->next) {
		descriptor_write(tmp_desc, "<script>SendToChat('ERROR: Chat server crash, please click <a target=_top href=");
		descriptor_write(tmp_desc, site_url);
		descriptor_write(tmp_desc, ">here</a> to log back in.');</script>\n");
		close(tmp_desc->descriptor);
	}
	sb_log(LOG_CRIT, "User notification of crash finished...");

	// 
	// Old code attempt... not necessary since this is being wrapped inside a shell script now.
	//
	
	//close(listener);
	//fclose(logfile);

	//fprintf(stderr, "before fork(): %d\n", getpid());
	//if ((forkpid = fork()) > 0) {
		//fprintf(stderr, "about to execl(): %d\n", getpid());
		//execl("bin/sbchat", "SBCHAT", "-l", "chat.log", (char *)NULL);
		//exit(0);
	//}

	//fprintf(stderr, "after fork(): %d\n", getpid());

	exit(1);
}

void bootstrap_signals (void) {
	struct sigaction ignore_sa, shutdown_sa, alarm_sa, child_sa, error_sa;

	sb_log(LOG_INFO, "Entering bootstrap_signals...");

	ignore_sa.sa_handler	= SIG_IGN;
	sigemptyset(&ignore_sa.sa_mask);
	ignore_sa.sa_flags	= SA_RESTART;

	shutdown_sa.sa_handler	= shutdown_handler;
	sigemptyset(&shutdown_sa.sa_mask);
	shutdown_sa.sa_flags	= SA_RESTART;

	error_sa.sa_handler	= error_handler;
	sigemptyset(&error_sa.sa_mask);
	error_sa.sa_flags	= SA_RESTART;

	child_sa.sa_handler	= child_handler;
	sigemptyset(&child_sa.sa_mask);
	child_sa.sa_flags	= SA_RESTART;

	alarm_sa.sa_handler	= SIG_IGN;
	//alarm_sa.sa_handler	= alarm_handler;
	sigemptyset(&alarm_sa.sa_mask);
	alarm_sa.sa_flags	= SA_RESTART;

	// Ignore warnings about pipes.
	sigaction( SIGPIPE,	&ignore_sa,	NULL );
	// Ignore the hangup signap.
	sigaction( SIGHUP,	&ignore_sa,	NULL );
	// Timer for checking lockups
	sigaction( SIGALRM,	&alarm_sa,	NULL );
	// Handle exiting childer processes
	sigaction( SIGCHLD,	&child_sa,	NULL );
	// Handle crashes
	sigaction( SIGBUS,	&error_sa,	NULL );
	sigaction( SIGSEGV,	&error_sa,	NULL );
	// Terminate a little more gracefully
	sigaction( SIGTERM,	&shutdown_sa,	NULL );

	alarm(300);
	sb_log(LOG_INFO, "Exiting bootstrap_signals...");
}

void shutdown_chat(void) {
	char	buffer[MAX_STRING_LENGTH];

	sb_log(LOG_INFO, "Shutting down...");

	if (dbconn != NULL) {
		//
		// note that chat is shutting down, for people that use client-pull
		//
		sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, user_id_to, event_text, event_date, event_chat_flags) VALUES (0, -1, 0, '<b>*** Chat server shutdown...</b>', NOW(), 2)", db_pfx);
		db_query(dbconn, buffer);

		sb_log(LOG_INFO, "Disconnecting from database");
		db_disconnect(dbconn);
	}

	if (logfile != NULL) {
		sb_log(LOG_INFO, "Closing log subsystem");
		fclose(logfile);
	}

	exit(0);
}
